package com.heima.schedule.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.common.constants.ScheduleConstants;
import com.heima.common.redis.CacheService;
import com.heima.model.schedule.dtos.TaskDto;
import com.heima.model.schedule.pojos.Taskinfo;
import com.heima.model.schedule.pojos.TaskinfoLogs;
import com.heima.schedule.mapper.TaskinfoLogsMapper;
import com.heima.schedule.mapper.TaskinfoMapper;
import com.heima.schedule.service.TaskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.*;

@Service
@Slf4j
@Transactional
public class TaskServiceImpl implements TaskService {
    @Autowired
    private TaskinfoMapper taskinfoMapper;
    @Autowired
    private TaskinfoLogsMapper taskinfoLogsMapper;
    @Autowired
    private CacheService cacheService;

    /**
     * 添加任务，返回任务id
     *
     * @param task
     * @return
     */
    public Long addTask(TaskDto task) {
        //1、将任务及其任务日志保存到数据库
        //1.1、通过雪花算法生成taskId
        long taskId = IdWorker.getId();
        task.setTaskId(taskId);
        boolean res = addTaskToDB(task);
        //2、将符合条件的任务添加到redis
        if (res) {
            addTaskToRedis(task);
        }
        return taskId;
    }

    /**
     * 取消任务
     *
     * @param taskId
     * @return
     */
    @Override
    public boolean cancelTask(Long taskId) {
        boolean flag = false;
        //1、删除任务表数据，更改日志表数据
        TaskDto task = updateDb(taskId, ScheduleConstants.CANCELLED);
        if (task != null) {
            //3、删除redis里数据
            removeTaskFromCache(task);
            flag = true;
        }
        return flag;
    }

    /**
     * 删除redis里面的数据 （取消任务）
     *
     * @param task
     */
    private void removeTaskFromCache(TaskDto task) {
        //1、准备key
        String key = task.getTaskType() + "_" + task.getPriority();
        //2、判断执行时间与系统时间
        if (task.getExecuteTime() <= System.currentTimeMillis()) {
            //删除list里面的
            // index = 0；删除所有等于value的值；
            cacheService.lRemove(ScheduleConstants.TOPIC + key, 0, JSON.toJSONString(task));//其实也就一个
        } else {
            //删除zset里的
            cacheService.zRemove(ScheduleConstants.FUTURE + key, JSON.toJSONString(task));
        }
    }

    /**
     * 修改数据库 （目前只用取消）
     *
     * @param taskId
     * @param status
     * @return
     */
    private TaskDto updateDb(Long taskId, int status) {
        TaskDto task = null;
        try {
            //1、根据id删除任务表数据
            taskinfoMapper.deleteById(taskId);
            //2、修改日志表状态
            //2、1、先根据id查询任务日志对象（后面需要）
            TaskinfoLogs taskinfoLogs = taskinfoLogsMapper.selectById(taskId);
            //2.2、修改状态
            taskinfoLogs.setStatus(status);
            //2.3、修改日志数据库
            taskinfoLogsMapper.updateById(taskinfoLogs);
            //3、创建taskDto对象返回
            task = new TaskDto();
            //3.1、属性拷贝
            BeanUtils.copyProperties(taskinfoLogs, task);
            //3.2、执行时间数据类型不同，不能拷贝 date -> long
            task.setExecuteTime(taskinfoLogs.getExecuteTime().getTime());
        } catch (BeansException e) {
            log.error("cancel task exception taskId:{}", taskId);
        }
        return task;
    }

    /**
     * 拉取任务（消费）
     *
     * @param taskType
     * @param priority
     * @return
     */
    public TaskDto popTask(Integer taskType, Integer priority) {
        //1、准备key
        String key = ScheduleConstants.TOPIC + taskType + "_" + priority;
        //2、将任务从redis的list中取出
        String taskJson = cacheService.lRightPop(key);
        //3、json转成TaskDto对象
        TaskDto taskDto = JSON.parseObject(taskJson, TaskDto.class);
        //4、将taskInfo删除 必须得加非空判断，不然消费任务定时执行，这边会疯狂报错
        if (taskDto != null) {
            taskinfoMapper.deleteById(taskDto.getTaskId());
            //5、将taskInfoLogs修改
            taskinfoLogsMapper.update(null, Wrappers.<TaskinfoLogs>lambdaUpdate()
                    .eq(TaskinfoLogs::getTaskId, taskDto.getTaskId())
                    .set(TaskinfoLogs::getStatus, ScheduleConstants.EXECUTED));

        }
        //6、返回结果
        return taskDto;
    }

    /**
     * 添加符合条件的任务到缓存
     * 1、当前任务时间小于等于系统时间 存入list
     * 2、预设时间（当前时间+5分钟） 大于等于 当前任务的执行时间  大于 系统时间
     *
     * @param task
     */
    private void addTaskToRedis(TaskDto task) {
        // key
        String key = task.getTaskType() + "_" + task.getPriority();

        //获取5分钟之后的时间，
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, 5);

        //判断
        if (task.getExecuteTime() <= System.currentTimeMillis()) {
            //1、当前任务时间小于等于系统时间 将任务存入list
            cacheService.lLeftPush(ScheduleConstants.TOPIC + key, JSON.toJSONString(task));
        } else if (task.getExecuteTime() <= calendar.getTimeInMillis()) {
            // 2、预设时间（当前时间+5分钟） 大于等于 当前任务的执行时间  大于 系统时间
            cacheService.zAdd(ScheduleConstants.FUTURE + key, JSON.toJSONString(task), task.getExecuteTime());
        }
    }

    /**
     * 添加数据到数据库
     *
     * @param task
     * @return
     */
    private boolean addTaskToDB(TaskDto task) {
        boolean flag = false;
        try {
            //1、先存入taskInfo
            Taskinfo taskinfo = new Taskinfo();
            //1.1、属性拷贝
            BeanUtils.copyProperties(task, taskinfo);
            //1.2、时间属性需要重新设置 long -> Date
            taskinfo.setExecuteTime(new Date(task.getExecuteTime()));
            //1.3、存入
            taskinfoMapper.insert(taskinfo);
            //2、再存入taskInfo
            //2.1、创建对象
            TaskinfoLogs logs = new TaskinfoLogs();
            //2.2、拷贝属性
            BeanUtils.copyProperties(taskinfo, logs);
            //2.3、版本
            logs.setVersion(1);
            //2.4、状态
            logs.setStatus(ScheduleConstants.SCHEDULED);
            //2.3、存入
            taskinfoLogsMapper.insert(logs);
            // 改变信号位
            flag = true;
        } catch (BeansException e) {
            e.printStackTrace();
        }
        return flag;
    }

    /**
     * redis定时刷新
     * 每分钟刷新一次，将ZSet中符合的数据刷新到list中
     * 定时
     */
    @Scheduled(cron = "0 */1 * * * ?") //每分钟执行一次
    public void refresh() {
       // log.info("redis定时刷新 每分钟刷新一次，将ZSet中符合的数据刷新到list中。。。");
        //1、取出zSet中的符合条件的数据 (key)（其实也就一种）
        Set<String> zSetKeys = cacheService.scan(ScheduleConstants.FUTURE + "*");
        //2、遍历，
        for (String zSetKey : zSetKeys) {
            //取出需要执行的任务对象（也就是执行时间小于当前系统时间了）
            Set<String> taskDtos = cacheService.zRangeByScore(zSetKey, 0, System.currentTimeMillis());
            //准备topicKey
            String topicKey = ScheduleConstants.TOPIC + zSetKey.split(ScheduleConstants.FUTURE)[1];
            //将符合条件的任务转到list中
            //--1、根据futureKey(zSetKey)找到对应的Zset，将里面对应的tasks删除
            //--2、根据topicKey找到对应的list，将里面对应的tasks添加
            if (taskDtos != null && taskDtos.size() > 0) { //代码健壮性
                log.info("redis定时刷新 每分钟刷新一次，成功将ZSet中符合的数据刷新到list中。。。");
                cacheService.refreshWithPipeline(zSetKey, topicKey, taskDtos);

            }
        }
    }

    /**
     * DB中的任务定时刷新到redis
     * 每五分钟刷新一次
     */
    @PostConstruct // Bean的初始化方法，当bean被创建好后，会自动执行该方法，只执行一次
    @Scheduled(cron = "0 */5 * * * ?") //每五分钟执行一次
    public void refreshToRedis() {
        //1、清空redis中任务
        clearCache();
        //2、查询数据库中未来五分钟前的任务
        //2.1、获取当前时间+ 5 分钟
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, 5);
        //2.2、查询数据（符合条件）
        List<Taskinfo> taskInfoList = taskinfoMapper.selectList(Wrappers.<Taskinfo>lambdaQuery().le(Taskinfo::getExecuteTime, calendar.getTime()));
        //3、将目标任务同步到redis 代码健壮性
        if (taskInfoList != null && taskInfoList.size() > 0) {
            //3.1、遍历
            for (Taskinfo taskinfo : taskInfoList) {
                //3.2、创建dto对象
                TaskDto dto = new TaskDto();
                //3.3、属性拷贝
                BeanUtils.copyProperties(taskinfo, dto);
                //3.4、时间
                dto.setExecuteTime(taskinfo.getExecuteTime().getTime());
                //3.5、添加到redis
                addTaskToRedis(dto);
            }
        }
    }

    /**
     * 清空缓存
     */
    private void clearCache() {
        Set<String> topicKeys = cacheService.scan(ScheduleConstants.TOPIC + "*");
        Set<String> zSetKeys = cacheService.scan(ScheduleConstants.FUTURE + "*");
        cacheService.delete(topicKeys);
        cacheService.delete(zSetKeys);
    }

    public static void main(String[] args) {
        String s = "TOPIC_10010_10";
        System.out.println(Arrays.toString(s.split("TOPIC_")));
    }

}
